Jetpack Compose Material 3 Pull-to-Refresh that can be disabled
I was upgrading an older Android project, the other day, to the latest Android version. It was built with the Jetpack Compose using its Material 3 (M3) design libraries. I recalled that it uses both M3, and the older Material 2 (M2), compose libraries because the pull-to-refresh component was poorly designed in M3. It’s better now, but it still lacks the enabled property of the original. Let’s fix that.
I was upgrading from M3 version 1.2.0, which has a PullToRefreshBox composable, but it used a function to stop the refreshing animation – refreshState.endRefresh(). With that API, it was impossible to stop the animation from a ViewModel or through a state variable. The API looked like this, seriously:
val refreshState = rememberPullToRefreshState(enabled = { enabled })
if (refreshState.isRefreshing) {
LaunchedEffect(true) {
onRefreshTrigger()
delay(1000)
refreshState.endRefresh()
}
}
Right now we have version 1.4.0, but a better pull-to-refresh component was already introduced in 1.3.0, and there it has a separate isRefreshing property. Now we can properly set the refreshing state from a ViewModel or a state variable.
I don’t want my list to have the pull gesture enabled when the initial data is loading, so I was looking for the enabled parameter, and no, it is still not available like in the original M2 implementation. Why Google, why???
At least it is easy to wrap in my own Composable, and here it is (MIT license):
@Composable
fun PullToRefreshBoxEnabled(
isRefreshing: Boolean,
onRefresh: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
state: PullToRefreshState = rememberPullToRefreshState(),
contentAlignment: Alignment = Alignment.TopStart,
indicator: @Composable BoxScope.() -> Unit = {
Indicator(
modifier = Modifier.align(Alignment.TopCenter),
isRefreshing = isRefreshing,
state = state,
)
},
content: @Composable BoxScope.() -> Unit,
) {
val disablePullConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset, source: NestedScrollSource
): Offset {
return available
}
override suspend fun onPreFling(
available: Velocity
): Velocity {
return available
}
}
}
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = {
if (enabled && !isRefreshing) {
onRefresh()
}
},
modifier = if (enabled) {
modifier
} else {
modifier.nestedScroll(disablePullConnection)
},
state = state,
contentAlignment = contentAlignment,
indicator = indicator
) {
content()
}
}
Why not release this as a library? As one Go proverb says, “A little copying is better than a little dependency.” – and I believe that Google will add the enabled property in a future release. Best of all, I can finally remove the androidx.compose.material:material dependency from my project.